use std::ffi::{c_char, c_int, c_uint, c_ushort, CStr, CString};

use tools::{error::Error, ResultEx};
use tracing::warn;

use super::context::{DialFailedReason, PendingReason, ShEvent};

#[link(name = "shpa3")]
extern "C" {
	fn SsmStartCtiEx(
		lpSsmCfgFileName: *const c_char,
		lpIndexCfgFileName: *const c_char,
		bEnable: bool,
		pEventSet: *const ShEventSetInfo,
	) -> c_int;
	pub(crate) fn SsmGetMaxCh() -> c_int;
	pub(crate) fn SsmCloseCti();

	fn SsmSearchIdleCallOutCh(wSearchMode: c_ushort, dwPrecedence: c_uint) -> c_int;
	fn SsmAutoDial(ch_id: c_uint, szPhoNum: *const c_char) -> c_int;
	fn SsmGetCallerIdA(ch_id: c_uint) -> *const c_char;
	fn SsmGetPhoNumStrA(ch_id: c_uint) -> *const c_char;
	fn SsmGetPendingReason(ch_id: c_uint) -> c_int;
	fn SsmGetAutoDialFailureReason(ch_id: c_uint) -> c_int;
	fn SsmSetTxCallerId(ch_id: c_uint, pszTxCallerId: *const c_char) -> c_int;
	fn SsmGetLastErrMsgA() -> *const c_char;
	fn SsmHangup(ch_id: c_uint) -> c_int;
}

pub(crate) fn hangup_channel(ch: u32) -> ResultEx<()> {
	let res = unsafe { SsmHangup(ch) };

	if res >= 0 {
		Ok(())
	} else {
		let er = get_last_error();
		Err(Error::DeviceOperatorError(ch, er))
	}
}

pub(crate) fn get_last_error() -> String {
	let err = unsafe {
		let res = SsmGetLastErrMsgA();
		CStr::from_ptr(res)
	}
	.to_str()
	.expect("SsmGetCallerIdA得到caller失败");

	err.to_owned()
}
pub(crate) fn originate(caller_num: &str, called_num: &str) -> ResultEx<u32> {
	let ch = search_idle_call_out_ch()?;
	let call_num = CString::new(called_num).expect("呼出:CString::new ");
	let caller = CString::new(caller_num).expect("呼出:CString::new ");

	let res = unsafe { SsmSetTxCallerId(ch, caller.as_ptr()) };
	if res < 0 {
		return Err(Error::DeviceOperatorError(
			ch,
			"设置通道主叫号码失败".to_owned(),
		));
	}

	let res = unsafe { SsmAutoDial(ch, call_num.as_ptr()) };
	if res >= 0 {
		Ok(ch)
	} else {
		let er = get_last_error();
		Err(Error::DeviceOperatorError(ch, er))
	}
}
pub(crate) fn search_idle_call_out_ch() -> ResultEx<u32> {
	//32是在ISUP通道搜索..0是对应参数
	let ch = unsafe { SsmSearchIdleCallOutCh(32, 0) };
	if ch >= 0 {
		return Ok(ch as u32);
	} else {
		return Err(Error::NoAvailableChannel);
	}
}

///返回挂断的原因.当原因为呼叫失败时,同时返回呼叫失败原因
/// 其他值无呼叫失败原因
pub(crate) fn pending_reason(ch_id: u32) -> (PendingReason, DialFailedReason) {
	let p_reason: PendingReason = unsafe { SsmGetPendingReason(ch_id) }.into();
	let d_f_reason: DialFailedReason = match p_reason {
		PendingReason::PendAutoDialFailed => unsafe { SsmGetAutoDialFailureReason(ch_id) }.into(),
		_ => DialFailedReason::UnknownValue,
	};

	(p_reason, d_f_reason)
}

///交给板卡的初始化参数用
#[repr(C)]
struct ShEventSetInfo {
	pub(crate) dw_work_mode: c_uint,
	pub(crate) lp_handler_param: extern "C" fn(c_ushort, c_int, c_uint, c_uint) -> c_int,
	pub(crate) dw_out_condition: c_uint,
	pub(crate) dw_out_param_val: c_uint,
	pub(crate) dw_user: c_uint,
}

///启动板卡,并且进行事件回调注册
pub(crate) fn start_cti_by_event_callback(
	sh_cfg_file: CString,
	index_cfg_file: CString,
	call_back_fn: extern "C" fn(c_ushort, c_int, c_uint, c_uint) -> c_int,
) -> c_int {
	//使用事件模式
	let event_set = ShEventSetInfo {
		dw_work_mode: 2,
		lp_handler_param: call_back_fn,
		dw_out_condition: 0,
		dw_out_param_val: 0,
		dw_user: 0,
	};

	unsafe {
		SsmStartCtiEx(
			sh_cfg_file.into_raw(),
			index_cfg_file.into_raw(),
			true,
			&event_set as *const ShEventSetInfo,
		)
	}
}

///返回当前可用通道的总数
#[inline]
pub(crate) fn count() -> usize {
	unsafe { SsmGetMaxCh() as usize }
}

pub fn close_synway() {
	warn!("调用板卡的关闭操作");
	unsafe {
		SsmCloseCti();
	}
}

pub(crate) fn get_caller_id(ch_id: u32) -> String {
	let caller_id = unsafe {
		let c_id = SsmGetCallerIdA(ch_id);
		CStr::from_ptr(c_id)
	}
	.to_str()
	.expect("SsmGetCallerIdA得到caller失败");

	caller_id.to_owned()
}

pub(crate) fn get_called_id(ch_id: u32) -> String {
	let called_id = unsafe {
		let c_id = SsmGetPhoNumStrA(ch_id);
		CStr::from_ptr(c_id)
	}
	.to_str()
	.expect("SsmGetPhoNumStrA得到caller失败");

	called_id.to_owned()
}

///板卡设备的事件包装器
#[derive(Debug)]
pub(crate) struct ShEventInfo {
	pub(crate) event: ShEvent,
	pub(crate) user: u32,
	pub(crate) n_reference: i32,
	pub(crate) dw_param: u32,
}
